package org.suwenqian.audiovideosummary.util;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qiniu.util.Auth;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import org.suwenqian.audiovideosummary.config.LLMProperties;
import org.suwenqian.audiovideosummary.config.QiNiuYunProps;
import org.suwenqian.audiovideosummary.dao.entity.Chapter;
import org.suwenqian.audiovideosummary.dto.resp.OutlineDTO;
import org.suwenqian.audiovideosummary.exception.LLMServiceException;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * 七牛云LLM服务工具类
 * 提供摘要生成和大纲提取功能
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class LLMUtil {

    private final LLMProperties props;       // LLM服务配置
    private final RestTemplate restTemplate; // REST调用模板
    private final ObjectMapper objectMapper; // JSON处理器
    private final QiNiuYunProps qnProps;    // 七牛云配置

    /**
     * 调用摘要生成接口
     * @param chunk 需要生成摘要的文本内容
     * @return 生成的摘要文本
     * @throws LLMServiceException 当调用服务失败时抛出
     */
    public String callSummary(String chunk) throws LLMServiceException {
        try {
            // 构建请求体
            Map<String, String> body = Map.of(
                    "text", chunk,
                    "lang", props.getDefaultLanguage(), // 从配置获取默认语言
                    "max_tokens", String.valueOf(props.getMaxSummaryTokens()) // 从配置获取最大token数
            );

            JsonNode response = postQiNiu(props.getSummaryEndpoint(), body);

            // 安全地获取结果节点
            JsonNode resultNode = response.path("result");
            if (resultNode.isMissingNode()) {
                throw new LLMServiceException("API响应缺少result字段");
            }

            JsonNode summaryNode = resultNode.path("summary");
            if (summaryNode.isMissingNode()) {
                throw new LLMServiceException("API响应缺少summary字段");
            }

            return summaryNode.asText();
        } catch (Exception e) {
            log.error("[大模型音频处理]--生成摘要文本大纲与思维导图任务--调用摘要生成接口失败: {}", e.getMessage());
            throw new LLMServiceException("生成摘要失败: " + e.getMessage());
        }
    }
    /**
     * 调用大纲提取接口
     * @param fullText 需要提取大纲的完整文本
     * @return OutlineDTO 包含章节和关键词
     * @throws LLMServiceException 当调用服务失败时抛出
     */
    public OutlineDTO callOutline(String fullText) throws LLMServiceException {
        try {
            // 构建请求体
            Map<String, String> body = Map.of(
                    "text", fullText,
                    "output_format", "json",
                    "chapters", String.valueOf(props.getDefaultChapterCount())
            );

            JsonNode response = postQiNiu(props.getOutlineEndpoint(), body);
            JsonNode resultNode = response.path("result");

            if (resultNode.isMissingNode()) {
                throw new LLMServiceException("API响应缺少result字段");
            }

            // 解析章节列表 - 使用treeToValue
            JsonNode chaptersNode = resultNode.path("chapters");
            List<Chapter> chapters = objectMapper.treeToValue(
                    chaptersNode,
                    objectMapper.getTypeFactory().constructCollectionType(List.class, Chapter.class)
            );

            // 解析关键词列表 - 使用安全转换方法
            JsonNode keywordsNode = resultNode.path("keywords");
            List<String> keywords = convertToStringList(keywordsNode);

            long cost = response.path("cost_ms").asLong(0);

            return new OutlineDTO(chapters, keywords, cost);
        } catch (Exception e) {
            log.error("[大模型音频处理]--生成摘要文本大纲与思维导图任务 -- 大模型调用发生错误: {}", e.getMessage());
            throw new LLMServiceException("提取大纲失败: " + e.getMessage());
        }
    }

    /**
     * 发送请求到七牛云API
     * @param url API端点URL
     * @param body 请求体内容
     * @return 响应JSON节点
     * @throws LLMServiceException 当调用失败时抛出
     */
    private JsonNode postQiNiu(String url, Map<String, ?> body) throws LLMServiceException {
        try {
            // 创建认证对象
            Auth auth = Auth.create(qnProps.getAk(), qnProps.getSk());

            // 生成请求签名，使用更安全的签名方式
            String token = auth.signRequest(url, body.toString().getBytes(StandardCharsets.UTF_8), "application/json");

            // 构建请求头
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            headers.set("Authorization", "QBox " + token);

            // 执行请求
            return restTemplate.exchange(
                    url,
                    HttpMethod.POST,
                    new HttpEntity<>(body, headers),
                    JsonNode.class
            ).getBody();
        } catch (RestClientException e) {
            log.error("[大模型音频处理]--生成摘要文本大纲与思维导图任务 -- 七牛云大模型API调用出错:  {}", e.getMessage());
            throw new LLMServiceException("七牛云服务调用失败: " + e.getMessage());
        }
    }

    /**
     * 安全转换JsonNode到String列表
     */
    private List<String> convertToStringList(JsonNode node) throws LLMServiceException {
        try {
            if (node == null || node.isNull()) {
                return Collections.emptyList();
            }

            if (node.isTextual()) {
                // 如果是单个文本节点，转为单元素列表
                return Collections.singletonList(node.asText());
            }

            if (!node.isArray()) {
                throw new LLMServiceException("keywords字段必须是数组或文本");
            }

            List<String> result = new ArrayList<>();
            for (JsonNode element : node) {
                if (!element.isTextual()) {
                    log.warn("忽略非文本类型的keyword元素: {}", element);
                    continue;
                }
                result.add(element.asText());
            }
            return result;
        } catch (Exception e) {
            throw new LLMServiceException("转换关键词列表失败");
        }
    }
}